package net.ib.mtalk.global;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import net.ib.mtalk.constant.NetworkConstant;
import net.ib.mtalk.network.NetFlowControl;
import net.ib.mtalk.network.TcpEnvelope;
import net.ib.mtalk.network.task.NetTaskHandler;
import net.ib.mtalk.service.BindCallback;
import net.ib.mtalk.service.SelecterService;
import net.ib.mtalk.sync.AsyncCheckVersion;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Build;
import android.os.IBinder;

import com.google.android.c2dm.C2DMessaging;

/**
 * 전역 어플리케이션 객체.<br>
 * <br>
 * 어플리케이션 Context를 얻을 수 있는 환경을 구현한다.<br>
 * 바인드된 로컬서비스의 객체를 얻을 수 있는 환경을 구현한다.<br>
 * @author Arngard
 *
 */
public class GlobalApplication extends Application {

	private static Context myContext = null;
	
	/*	서비스 바인딩 START	*/
	public static final int SERVICE_UNBINDED = 0;
	public static final int SERVICE_REQUESTED = 1;
	public static final int SERVICE_BINDED = 2;

	private int service_state = 0;
	private SelecterService mService = null;//private LocalService mService = null;

	Lock lock = new ReentrantLock();
	/*	서비스 바인딩 END	*/

	/**
	 * 인자로 준 액티비티가 종속되어 있는 AppGlobal 객체를 얻어온다.
	 * 이 때 얻어오는 객체는 어플리케이션의 모든 실행 요소가 제거(Task kill 등)되지 않는 한 계속 살아있는 객체이다.<br>
	 * @param activity 생성이 완료되지 않은 액티비티를 사용하지 않도록 주의한다.
	 * 그런 경우에는 내부 요소가 모두 구성되었으리라고 보장할 수 없다. onCreate() 등이 호출된 이후의 객체를 사용하도록 한다.
	 * @return activity.getApplication()
	 */
	public static GlobalApplication of(Activity activity) {
		return (GlobalApplication)(activity.getApplication());
	}

	public static Context getContext() {
		return myContext;
	}
	
	public static Resources getResource() {
		return myContext.getResources();
	}
	

	/*	서비스 바인딩 START	*/

	private ServiceConnection mConnection = new ServiceConnection() {
		/**
		 * 서비스 바인드 상태 플래그를 갱신한다.
		 * @see android.content.ServiceConnection#onServiceDisconnected(android.content.ComponentName)
		 */
		@Override
		public void onServiceDisconnected(ComponentName name) {
			service_state = SERVICE_UNBINDED;
			mService = null;
		}

		/**
		 * 바인드된 서비스의 객체를 획득하여 저장한다.
		 * 서비스 바인드 상태 플래그를 갱신한다.
		 * @see android.content.ServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder)
		 */
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mService = ((SelecterService.LocalBinder)service).getService();//((LocalService.LocalBinder)service).getService();
			service_state = SERVICE_BINDED;
		}
	};

	class AsyncBind extends AsyncTask<Context, Void, Void> {
		/**
		 * 서비스를 실행하고 바인드한다.
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 */
		@Override
		protected Void doInBackground(Context... params) {
			Context context = params[0];

			//try {	// for test
			//	Thread.sleep(3*1000);
			//} catch (InterruptedException e) {
			//	e.printStackTrace();
			//}

			SelecterService.checkAndStart(context);//LocalService.checkAndStart(context);
			SelecterService.actionBind(context, mConnection);//LocalService.actionBind(context, mConnection);
			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			cancel(true);
		}

		@Override
		protected void onCancelled() {
			super.onCancelled();
			try {
				finalize();
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}

	class AsyncGetService extends AsyncTask<Void, Void, SelecterService> {//<Void, Void, LocalService> {
		BindCallback bindCallback = null;

		public AsyncGetService(BindCallback bindCallback) {
			this.bindCallback = bindCallback;
		}

		@Override
		protected SelecterService doInBackground(Void... params) {
			lock.lock();
			try{
				if (mService == null) {
					mService = getService();	// 서비스 정보가 없으면 받아오자
					return mService;
				}
				return null;	// 이미 서비스 정보가 있었다.
			}finally{
				lock.unlock();
			}
		}

		@Override
		protected void onPostExecute(SelecterService result) {
			super.onPostExecute(result);
			if (result == null) {	// 이미 서비스 정보가 있어서 새로 얻어오지 않았다.
				bindCallback.onRebind(mService);
			} else {	// 새로 얻어온 서비스 정보가 있다.
				bindCallback.onNewBind(result);
			}
			cancel(true);
		}

		@Override
		protected void onCancelled() {
			super.onCancelled();
			try {
				finalize();
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}

	// TODO rebind()와 getService()에서 플래그 체크-세팅 부분에 락을 걸어야 맞는 것 아닌지?

	/**
	 * 바인드 요청이 되지 않았다면 바인드 요청을 한다.
	 * 이미 바인드되어 있거나 요청이 진행중이면 아무 일도 하지 않는다. 
	 */
	private void rebind() {
		if (service_state == SERVICE_BINDED && mService != null) {	// 이미 완료된 요청일 때
			return;
		} else if (service_state == SERVICE_REQUESTED) {	// 이미 진행중인 요청일 때
			return;
		}

		AsyncBind bind = new AsyncBind();
		bind.execute(this);
		service_state = SERVICE_REQUESTED;
	}

	/**
	 * 바인딩된 서비스의 객체를 가져온다.
	 * 만일 바인드되어 있지 않다면 바인드 요청을 한 후, 바인드가 성립될 때까지 기다려(Thread.sleep()) 리턴한다.
	 * 이미 바인드되어 있었다면, 바인드 성공시에 얻어내어 기억하고 있었던 서비스 객체를 돌려준다.
	 * @return 바인딩된 서비스의 객체
	 */
	private SelecterService getService() {//private LocalService getService() {
		if (service_state == SERVICE_BINDED && mService != null) {
			return mService;
		} else if (service_state == SERVICE_UNBINDED) {
			// 서비스가 죽어있다?
			rebind();
		}

		while(service_state != SERVICE_BINDED || mService == null) { // 서비스가 바인드될 때까지 기다린다.
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return mService;
	}

	/**
	 * 서비스 측과의 싱크로를 확인한다.
	 * <br>
	 * 바인딩된 서비스의 정보를 가지고 있지 않다면, 비동기적으로 바인드를 시도하게 될 것이다.
	 * 이 경우 바인드에 성공한 시점에 {@link BindCallback#onNewBind(SelecterService) bindCallback.onNewBind(SelecterService)}가 호출된다.<br>
	 * <br>
	 * 바인딩된 서비스의 객체를 이미 가지고 있다면, 그것을 확인한 시점에 {@link BindCallback#onRebind(SelecterService) bindCallback.onRebind(SelecterService)}가 호출된다.
	 * @param bindCallback
	 */
	public void checkService(BindCallback bindCallback) {
		AsyncGetService getService = new AsyncGetService(bindCallback);
		getService.execute();
	}

	/*	서비스 바인딩 END	*/

	/**
	 * 시작. 비동기적으로 서비스의 실행과 바인드를 시도한다.
	 * @see android.app.Application#onCreate()
	 */
	@Override
	public void onCreate() {
		super.onCreate();

		myContext = getApplicationContext();
		GlobalPreferences.setSharedPreferences(myContext);
		
		TcpEnvelope.resetRequestId();
		NetTaskHandler.getInstance();	// AsyncNetTask worker 스레드 생성.
		NetFlowControl.readyClientApi();	// client api 요청에 대비한 기본 콜백들 생성
		
		if (false) {	// TODO
			rebind();
		} else {
			if (GlobalPreferences.getRegistrationId() == null) {	// C2DM이 등록되어 있지 않다면
				if (Build.VERSION.SDK_INT >= 8) {	// C2DM을 쓸 수 있는 단말이라면
					C2DMessaging.register(GlobalApplication.getContext(), NetworkConstant.C2DM_SENDER_ID);	// 등록 시도
				}
				rebind();	// 등록까지 되어 있었다면 여기서 서비스 실행할 필요가 없었을 것이다.
			}
		}
	}
}
